package com.cens.mall.seckill.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.cens.common.to.SeckillOrderTO;
import com.cens.common.utils.R;
import com.cens.common.vo.MemberEntityVo;
import com.cens.mall.seckill.feign.CommodityFeignService;
import com.cens.mall.seckill.feign.CouponFeignService;
import com.cens.mall.seckill.interceptor.LoginIntercepter;
import com.cens.mall.seckill.to.SeckillSessionRedisTo;
import com.cens.mall.seckill.vo.SeckillSessionVo;
import com.cens.mall.seckill.vo.SeckillSkuRelationVo;
import com.cens.mall.seckill.vo.SkuInfoVo;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Service
public class SeckillServiceImpl implements SeckillService {

    @Autowired
    private CouponFeignService couponFeignService;
    @Autowired
    private CommodityFeignService commodityFeignService;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RabbitTemplate rabbitTemplate;

    private final String SESSIONS_CACHE_PREFIX = "seckill:sessions:";
    private final String SKUKILL_CACHE_PREFIX = "seckill:skus:";
    private final String SKU_STOCK_SEMAPHORE = "seckill:stock:";

    @Override
    public void uploadSeckillSku() {
        //查询需要参与秒杀活动的商品
        R session = couponFeignService.getLatest3DaysSession();
        if (session.getCode() == 0) {
            List<SeckillSessionVo> sessionData = session.getData(new TypeReference<List<SeckillSessionVo>>() {
            });
            //缓存活动到 redis
            saveSessionInfos(sessionData);
            //缓存活动商品到 redis
            saveSEssionSkuInfos(sessionData);
        }
    }

    @Override
    public List<SeckillSessionRedisTo> getCurrentSeckillSkus() {
        //1、确定当前时间属于哪个秒杀场次
        long time = System.currentTimeMillis();
        Set<String> keys = redisTemplate.keys(SESSIONS_CACHE_PREFIX + "*");
        for (String key : keys) {
            String replace = key.replace(SESSIONS_CACHE_PREFIX, "");
            String[] s = replace.split("_");
            long startTime = Long.parseLong(s[0]);
            long endTime = Long.parseLong(s[1]);
            if (time >= startTime && time <= endTime) {
                List<String> range = redisTemplate.opsForList().range(key, -100, 100);
                BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
                List<String> list = ops.multiGet(range);
                if (list != null) {
                    return list.stream()
                            .map(to -> JSON.parseObject(to, SeckillSessionRedisTo.class))
//                            .peek(to -> to.setRandomCode(null))
                            .collect(Collectors.toList());
                }
            }
        }
        return null;
    }

    @Override
    public SeckillSessionRedisTo getSkuSeckillInfo(Long skuId) {
        BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
        Set<String> keys = ops.keys();
        if (keys != null && keys.size() > 0) {
            String regx = "\\d_"+skuId;
            for (String key : keys) {
                if(Pattern.matches(regx,key)){
                    String s = ops.get(key);
                    SeckillSessionRedisTo seckillSessionRedisTo = JSON.parseObject(s, SeckillSessionRedisTo.class);
                    Long startTime = seckillSessionRedisTo.getStartTime();
                    Long endTime = seckillSessionRedisTo.getEndTime();
                    long current = System.currentTimeMillis();
                    if(current >= startTime && current <= endTime){}
                    else{
                        seckillSessionRedisTo.setRandomCode(null);
                    }
                    return seckillSessionRedisTo;
                }
            }
        }
        return null;
    }

    @Override
    public String kill(String killId, String key, Integer num) {

        MemberEntityVo memberEntityVo = LoginIntercepter.loginUser.get();
        //获取秒杀商 品的详细信息
        BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
        String seckillInfo = ops.get(killId);
        if(StringUtils.isNotEmpty(seckillInfo)){
            SeckillSessionRedisTo seckillSessionRedisTo = JSON.parseObject(seckillInfo, SeckillSessionRedisTo.class);
            Long startTime = seckillSessionRedisTo.getStartTime();
            Long endTime = seckillSessionRedisTo.getEndTime();
            long time = System.currentTimeMillis();
            //校验时间的合法性
            if(time >= startTime && time <= endTime){
                //校验随机码
                String randomCode = seckillSessionRedisTo.getRandomCode();
                if(Objects.equals(key,randomCode)){
                    //验证限购数量
                    if(num <= seckillSessionRedisTo.getSeckillLimit().intValue()){
                        //校验是否购买过
                        String rKey = memberEntityVo.getId() + "_"+seckillSessionRedisTo.getPromotionSessionId()+"_"+seckillSessionRedisTo.getSkuId();
                        //自动过期
                        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(rKey, num.toString(), endTime-time, TimeUnit.MILLISECONDS);
                        if(aBoolean != null && aBoolean){
                            try {
                                boolean acquire = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode).tryAcquire(num, 100, TimeUnit.MILLISECONDS);
                                if(acquire){
                                    //秒杀成功
                                    //快速下单。 发送MQ消息
                                    String timeId = IdWorker.getTimeId();
                                    rabbitTemplate.convertAndSend("order-event-exchange",
                                            "order.seckill.order",
                                            SeckillOrderTO.builder()
                                                    .orderSn(timeId)
                                                    .memberId(memberEntityVo.getId())
                                                    .num(num)
                                                    .promotionSessionId(seckillSessionRedisTo.getPromotionSessionId())
                                                    .seckillPrice(seckillSessionRedisTo.getSeckillPrice())
                                                    .skuId(seckillSessionRedisTo.getSkuId())
                                                    .build());
                                    return timeId;
                                }
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
        return null;
    }

    private void saveSEssionSkuInfos(List<SeckillSessionVo> sessionData) {
        sessionData.forEach(
                session -> {
                    BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
                    session.getSkuRelationEntityList().forEach(
                            sku -> {
                                String randomCode = UUID.randomUUID().toString().replace("-", "");
                                if (!ops.hasKey(sku.getPromotionSessionId() + "_" + sku.getSkuId())) {
                                    SeckillSessionRedisTo seckillSessionRedisTo = new SeckillSessionRedisTo();
                                    BeanUtils.copyProperties(sku, seckillSessionRedisTo);
                                    //查询商品sku信息
                                    R r = commodityFeignService.info(sku.getSkuId());
                                    if (r.getCode() == 0) {
                                        SkuInfoVo skuInfoVo = r.getData("skuInfo", new TypeReference<SkuInfoVo>() {
                                        });
                                        seckillSessionRedisTo.setSkuInfoVo(skuInfoVo);
                                    }
                                    //设置开始、结束时间
                                    seckillSessionRedisTo.setStartTime(session.getStartTime().getTime());
                                    seckillSessionRedisTo.setEndTime(session.getEndTime().getTime());
                                    //生成随机码
                                    seckillSessionRedisTo.setRandomCode(randomCode);
                                    //缓存商品信息
                                    ops.put(sku.getPromotionSessionId() + "_" + sku.getSkuId(), JSON.toJSONString(seckillSessionRedisTo));
                                    //商品可以秒杀的数量作为信号量（限流）
                                    redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode).trySetPermits(seckillSessionRedisTo.getSeckillCount().intValue());
                                }
                            }
                    );
                }
        );

    }

    private void saveSessionInfos(List<SeckillSessionVo> sessionData) {
        sessionData.forEach(
                session -> {
                    long startTime = session.getStartTime().getTime();
                    long endTime = session.getEndTime().getTime();
                    String key = SESSIONS_CACHE_PREFIX + startTime + "_" + endTime;

                    if (!redisTemplate.hasKey(key)) {
                        List<String> skuIds = session.getSkuRelationEntityList().stream()
                                .map(sku -> sku.getPromotionSessionId() + "_" + sku.getSkuId())
                                .collect(Collectors.toList());
                        redisTemplate.opsForList().leftPushAll(key, skuIds);
                    }
                }
        );
    }
}
